﻿/*    
Nemulation: A .NET based framework/platform for virtualization applications
Copyright (C) 2011 Bryan Perris

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Xml;
using NEmu;
using NEmu.Services;
using NEmuVirtualHost.CommandLine;
using NEmuVirtualHost.Configuration;
using NEmuVirtualHost.Services;

namespace NEmuVirtualHost
{
    class Program
    {
        private static ServicesProvider s_Services = new ServicesProvider();
        private static HostConfigReader s_HostConfigReader;
        private static ServerShell s_ShellHost;
        private static Pipeline s_ShellPipe = null;
        private static EventWaitHandle s_PipeCompleteWait = new EventWaitHandle(false, EventResetMode.AutoReset);
        private static Thread s_ShellPrintThread;
        private static KeyValuePair<string, string>[] s_HostSettings;

        static void Main(string[] args)
        {
            Console.SetBufferSize(Console.BufferWidth, 3000);
            Console.SetWindowSize(120, 50);
            Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);

            Console.WriteLine("===================================================================");
            Console.WriteLine("NEmulation Virtual Machine Server " + Assembly.GetExecutingAssembly().GetName().Version.ToString());
            Console.WriteLine("Copyright (c) Bryan Perris 2011");
            Console.WriteLine("Notice: This comes with ABSOLUTELY NO WARRANTY;");
            Console.WriteLine("        This is free software, and you are welcome to");
            Console.WriteLine("        redistribute it under certain conditions\n");
            Console.WriteLine("Target CLR Runtime: " + Environment.Version.ToString());
            Console.WriteLine("Target Operating System: " + Environment.OSVersion.VersionString);
            Console.WriteLine("===================================================================\n");

            // Load host settings
            try
            {
                s_HostConfigReader = new HostConfigReader();
                s_HostSettings = new KeyValuePair<string, string>[s_HostConfigReader.Count];
                s_HostConfigReader.CopyTo(s_HostSettings, 0);
            }
            catch (XmlException e)
            {
                Console.WriteLine("Config file load exception: " + e.Message);
                GoToError();
            }

            // Load assemblies
            LoadAssembliesFromConfig(s_HostConfigReader);
        
            // Load services
            Console.WriteLine("\nLoading services:\n");
            s_Services.HostOpened += new EventHandler<ServiceHostEventArgs>(s_Services_HostOpened);
            s_Services.AddService(new ServiceReflector(Assembly.LoadFrom("NEmu.Services.dll")));
            s_Services.RunServices();
            Console.WriteLine("\nDone\n");
            
            // Start the powershell host
            StartShellInterface();

            // Close the services
            s_Services.CloseServices();
        }

        static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = true;

            if (s_ShellPipe != null)
            {
                if (s_ShellPipe.PipelineStateInfo.State == PipelineState.Running)
                {
                    s_ShellPipe.StopAsync();
                }
            }
        }

        static void StartShellInterface()
        {
            // Runs the powershell interactive mode
            s_ShellHost = new ServerShell();
            bool exit = false;
            string read;

            Console.WriteLine("Type \"exit\" to close the server and end all running services");

            using (Runspace space = RunspaceFactory.CreateRunspace(s_ShellHost))
            {
                space.Open();
                Console.WriteLine("PowerShell host successfully initialized.");

                // Main interface loop
                while (!exit)
                {
                    Console.Write("PS Server>");
                    read = Console.ReadLine();

                    // If the user pressed Ctrl + C
                    if (read == null)
                    {
                        Console.WriteLine("");
                        continue;
                    }

                    // Handle exit
                    if (read.ToLower().Equals("exit"))
                        break;

                    // Create a new pipeline, then invoke it
                    s_ShellPipe = space.CreatePipeline(read);
                    s_ShellPipe.InvokeAsync();

                    // Create the printer thread
                    s_ShellPrintThread = new Thread(new ThreadStart(AsyncPipeRead));
                    s_ShellPrintThread.IsBackground = true;
                    s_ShellPrintThread.Name = "Async Pipeline Read Thread";
                    s_ShellPrintThread.Start();

                    // Wait for the pipeline reading async operations to finish
                    s_PipeCompleteWait.WaitOne();

                    // If there is any output left, print it out
                    while (!s_ShellPipe.Output.EndOfPipeline && s_ShellPipe.Output.Count > 0)
                        Console.WriteLine(s_ShellPipe.Output.Read());

                    // If anything failed, then print out the errors
                    if (s_ShellPipe.PipelineStateInfo.State == PipelineState.Failed)
                        PrintPSException(s_ShellPipe.PipelineStateInfo.Reason.GetBaseException());
                }
            }
        }

        static void PrintPSException(Exception e)
        {
            StringBuilder message = new StringBuilder();

            if (e.GetType().Equals(typeof(RuntimeException)))
            {
                if (e.GetType().Equals(typeof(CommandNotFoundException)))
                {
                    message.AppendLine(((CommandNotFoundException)e).Message);
                }

                if (e.GetType().Equals(typeof(ParameterBindingException)))
                {
                    ParameterBindingException pbe = (ParameterBindingException)e;
                    message.Append(pbe.CommandInvocation.MyCommand + " : " + e.Message);
                    message.AppendLine(pbe.CommandInvocation.PositionMessage);
                }

                message.AppendLine("    + CategoryInfo          :" + ((RuntimeException)e).ErrorRecord.CategoryInfo.ToString());
                message.AppendLine("    + FullyQualifiedErrorId :" + ((RuntimeException)e).ErrorRecord.FullyQualifiedErrorId);
            }
            else
            {
                message.AppendLine(e.Message);
            }

            if (s_ShellHost != null)
                s_ShellHost.UI.WriteErrorLine(message.ToString());
        }

        static void AsyncPipeRead()
        {
            if (s_ShellPipe != null)
            {
                while (s_ShellPipe.PipelineStateInfo.State == PipelineState.Running)
                {
                    if (s_ShellPipe.Output.Count > 0 && !s_ShellPipe.Output.EndOfPipeline)
                    {
                        Console.WriteLine(s_ShellPipe.Output.Read());
                    }

                    Thread.Sleep(1);
                }
            }

            s_PipeCompleteWait.Set();
        }

        static void s_Services_HostOpened(object sender, ServiceHostEventArgs e)
        {
            PrintServiceHostInfo(e.host);
        }

        static void PrintServiceHostInfo(ServiceHost host)
        {
            string firstline = "--- Service (" + host.Description.ServiceType.Name + ") Running -------------";

            Console.WriteLine(firstline);

            foreach (Uri uri in host.BaseAddresses)
                Console.WriteLine("Host Address: " + uri.ToString());

            Console.WriteLine("Communication State: " + host.State.ToString());
            Console.WriteLine("Open Timeout: " + host.OpenTimeout.ToString());
            Console.WriteLine("Close Timout: " + host.CloseTimeout.ToString());

            for (int i = 0; i < firstline.Length; i++)
                Console.Write("-");

            Console.WriteLine("");
        }

        static void LoadAssembliesFromConfig(HostConfigReader reader)
        {
            List<AssemblyName> assemblyReferences = new List<AssemblyName>();
            Console.Write("\nAssemblies loaded by configuration: ");
            List<string> asses = reader.GetAssemblyReferenceList();

            if (asses.Count > 0)
            {
                foreach (string assName in asses)
                {
                    Assembly ass = Assembly.LoadFrom(assName);
                    Console.WriteLine("Assembly: " + ass.FullName);
                    assemblyReferences.Add(ass.GetName());
                }
            }
            else
            {
                Console.Write("None");
            }

            Console.WriteLine("\n");
        }

        static void GoToError()
        {
            Console.WriteLine("\nAn unexpected error has occured, press any key to quit");
            Console.ReadKey();
            Environment.Exit(1);
        }
    }
}
